using System;
using System.Collections.Generic;
using System.Text;

namespace BigNumber
{

  public class RationalBigNumber<T> : IRationalBigNumber, IComparable<RationalBigNumber<T>>
    where T : IIntegerBigNumber, new()
  {
    private T m_Numerator;
    private T m_Denominator;
    private bool m_IsNegative;

    #region Helper Functions
    private void Minimize()
    {
      IBigNumber gcd = BigNumberMath.GCD(m_Numerator, m_Denominator);
      m_Numerator = (T)m_Numerator.IDiv(gcd);
      m_Denominator = (T)m_Denominator.IDiv(gcd);
    }
    #endregion

    #region Constructors
    public RationalBigNumber(string number)
    {
      Initialize(number);
    }

    /// <summary>
    /// Creates a new StdRationalNumber 
    /// </summary>
    /// <param name="numerator"></param>
    /// <param name="denumerator"></param>
    public RationalBigNumber(T numerator, T denominator)
    {
      if (denominator.IsZero)
        throw new ArgumentException("The denominator of a rational number cannot be 0");
      m_Denominator = (T)denominator.IClone();
      m_Denominator.IAbs();
      m_Numerator = (T)numerator.IClone();
      m_Numerator.IAbs();
      m_IsNegative = false;
      Minimize();
    }

    private RationalBigNumber(RationalBigNumber<T> number):this(number.m_Numerator,number.m_Denominator)
    {
      m_IsNegative = number.IsNegative;
    }

    #endregion

    #region Properties
    public bool IsZero
    {
      get { return m_Numerator.IsZero; }
    }

    public bool IsOne
    {
      get
      {
        return (m_Denominator.IEqual(m_Numerator));
      }
    }

    public bool IsNegative
    {
      get { return m_IsNegative; }
      private set 
      { 
        if (!IsZero)
          m_IsNegative = value; 
      }
    }

    public T Numerator
    {
      get { return (T)m_Numerator.IClone(); }
    }

    public T Denominator
    {
      get { return (T)m_Denominator.IClone(); }
    }

    #endregion

    #region Methods
    public void Initialize(string number)
    {
      string[] afterSplit = number.Split('/');
      if (afterSplit.Length == 0 || afterSplit.Length > 2)
        throw new Exception(String.Format("Incorrect rational format in constructor {0}", number));
      if (afterSplit.Length == 1)
      {
        m_Denominator = new T();
        m_Denominator.Initialize("1");
      }
      else
      {
        m_Denominator = new T();
        m_Denominator.Initialize(afterSplit[1]);
      }
      if (m_Denominator.IsZero)
        throw new DivideByZeroException("Divide by zero in constructor with number " + number);
      m_Numerator = new T();
      m_Numerator.Initialize(afterSplit[0]);
      m_IsNegative = m_Denominator.IsNegative != m_Numerator.IsNegative;
      m_Denominator.IAbs();
      m_Numerator.IAbs();
    }
    
    public void Negate()
    {
      IsNegative = !IsNegative;
    }


    #endregion

    #region Overrides
    public override string ToString()
    {
      string minus = m_IsNegative ? "-" : "";
      return  minus + m_Numerator.ToString() + "/" + m_Denominator.ToString();
    }

    public override bool Equals(object obj)
    {
      if (!(obj is RationalBigNumber<T>)) throw new InvalidCastException(String.Format("Cannot compare {0} to {1}", GetType(), obj.GetType()));
      RationalBigNumber<T> temp = obj as RationalBigNumber<T>;
      return this == temp;
    }
    #endregion

    #region Private Static Functions
    private static RationalBigNumber<T> Add(RationalBigNumber<T> firstNumber, RationalBigNumber<T> secondNumber)
    {
      T top1 = (T)(firstNumber.m_Numerator.IMul(secondNumber.m_Denominator));
      T top2 = (T)(firstNumber.m_Denominator.IMul(secondNumber.m_Numerator));
      T bottom = (T)(firstNumber.m_Denominator.IMul(secondNumber.m_Denominator));
      T top;
      if (firstNumber.IsNegative == secondNumber.IsNegative)
        top = (T)top1.IAdd(top2);
      else
      {
        if (firstNumber.IsNegative)
          top = (T)top2.ISub(top1);
        else
          top = (T)top1.ISub(top2);
      }
      bool negativity = top.IsNegative;
      RationalBigNumber<T> result = new RationalBigNumber<T>(top, bottom);
      result.IsNegative = negativity;
      result.Minimize();
      return result;
    }

    private static RationalBigNumber<T> Subtract(RationalBigNumber<T> firstNumber, RationalBigNumber<T> secondNumber)
    {
      T top1 = (T)(firstNumber.m_Numerator.IMul(secondNumber.m_Denominator));
      T top2 = (T)(firstNumber.m_Denominator.IMul(secondNumber.m_Numerator));
      T bottom = (T)(firstNumber.m_Denominator.IMul(secondNumber.m_Denominator));
      T top;
      if (firstNumber.IsNegative == secondNumber.IsNegative)
        top = (T)top1.ISub(top2);
      else
        top = (T)top1.ISub(top2);

      bool negativity = firstNumber.IsNegative;
      RationalBigNumber<T> result = new RationalBigNumber<T>(top, bottom);
      result.IsNegative = negativity;
      result.Minimize();
      return result;
    }

    private static RationalBigNumber<T> Multiply(RationalBigNumber<T> firstNumber, RationalBigNumber<T> secondNumber)
    {
      T top = (T)(firstNumber.Numerator.IMul(secondNumber.Numerator));
      T bottom = (T)(firstNumber.Denominator.IMul(secondNumber.Denominator));
      RationalBigNumber<T> result = new RationalBigNumber<T>(top, bottom);
      result.IsNegative = (firstNumber.IsNegative != secondNumber.IsNegative);
      result.Minimize();
      return result;
    }

    private static RationalBigNumber<T> Divide(RationalBigNumber<T> firstNumber, RationalBigNumber<T> secondNumber)
    {
      if (secondNumber.IsZero)
        throw new DivideByZeroException("Division by zero in Divide for StdRationalNumber");
      T top = (T)(firstNumber.Numerator.IMul(secondNumber.Denominator));
      T bottom = (T)(firstNumber.Denominator.IMul(secondNumber.Numerator));
      RationalBigNumber<T> result = new RationalBigNumber<T>(top, bottom);
      result.IsNegative = (firstNumber.IsNegative != secondNumber.IsNegative);
      result.Minimize();
      return result;
    }
    #endregion

    #region Operators
    public static RationalBigNumber<T> operator +(RationalBigNumber<T> firstNumber, RationalBigNumber<T> secondNumber)
    {
      return Add(firstNumber, secondNumber);
    }

    public static RationalBigNumber<T> operator -(RationalBigNumber<T> firstNumber, RationalBigNumber<T> secondNumber)
    {
      return Subtract(firstNumber, secondNumber);
    }

    public static RationalBigNumber<T> operator *(RationalBigNumber<T> firstNumber, RationalBigNumber<T> secondNumber)
    {
      return Multiply(firstNumber, secondNumber);
    }

    public static RationalBigNumber<T> operator /(RationalBigNumber<T> firstNumber, RationalBigNumber<T> secondNumber)
    {
      return Divide(firstNumber, secondNumber);
    }

    public static bool operator ==(RationalBigNumber<T> firstNumber, RationalBigNumber<T> secondNumber)
    {
      return (firstNumber.Denominator.IEqual(secondNumber.Denominator) && firstNumber.Numerator.IEqual(secondNumber.Numerator));
    }

    public static bool operator !=(RationalBigNumber<T> firstNumber, RationalBigNumber<T> secondNumber)
    {
      return !(firstNumber == secondNumber);
    }

    public static bool operator <(RationalBigNumber<T> firstNumber, RationalBigNumber<T> secondNumber)
    {
      if (firstNumber.IsNegative && !secondNumber.IsNegative) return true;
      if (!firstNumber.IsNegative && secondNumber.IsNegative) return false;
      RationalBigNumber<T> result = (RationalBigNumber<T>)firstNumber.ISub(secondNumber);
      if (!result.IsNegative && firstNumber.IsNegative)
        return true;
      if (result.IsNegative && firstNumber.IsNegative)
        return false;
      if (!result.IsNegative && !firstNumber.IsNegative)
        return false;
      if (result.IsNegative && !firstNumber.IsNegative)
        return true;
      return true;
    }

    public static bool operator >(RationalBigNumber<T> firstNumber, RationalBigNumber<T> secondNumber)
    {
      if (firstNumber.IsNegative && !secondNumber.IsNegative) return false;
      if (!firstNumber.IsNegative && secondNumber.IsNegative) return true;
      RationalBigNumber<T> result = (RationalBigNumber<T>)firstNumber.ISub(secondNumber);
      if (!result.IsNegative && firstNumber.IsNegative)
        return false;
      if (result.IsNegative && firstNumber.IsNegative)
        return true;
      if (!result.IsNegative && !firstNumber.IsNegative)
        return true;
      if (result.IsNegative && !firstNumber.IsNegative)
        return false;
      return false;
    }


    #endregion


    public double ToDouble()
    {
      
      IBigNumber whole = Numerator.IDiv(Denominator);
      IBigNumber part = Numerator.IMod(Denominator);
      Double prev = 0;
      Double result = 0;
      bool parsed = Double.TryParse(whole.ToString(),out result);
      if (!parsed)
        throw new InvalidOperationException("Unable to parse the integer part of the rational number into double value");
      IBigNumber ten = part.IConstruct("10");
      int count = 1;
      do
      {
        prev = result;
        part = part.IMul(ten);
        whole = part.IDiv(Denominator);
        part = part.IMod(Denominator);
        double temp = Convert.ToDouble(whole.ToString());
        for (int i = 0; i < count && temp > 0; i++)
          temp = temp / 10;
        count++;
        result = prev + temp;
      }
      while (prev != result);
      return result;
    }

    #region IBigNumber Members


    public IBigNumber IAdd(IBigNumber second)
    {
      RationalBigNumber<T> sSecond = second as RationalBigNumber<T>;
      return this + sSecond;
    }

    public IBigNumber ISub(IBigNumber second)
    {
      RationalBigNumber<T> sSecond = second as RationalBigNumber<T>;
      return this - sSecond;
    }

    public IBigNumber IMul(IBigNumber second)
    {
      RationalBigNumber<T> sSecond = second as RationalBigNumber<T>;
      return this * sSecond;
    }

    public IBigNumber IDiv(IBigNumber second)
    {
      RationalBigNumber<T> sSecond = second as RationalBigNumber<T>;
      return this / sSecond;
    }

    public IBigNumber IMod(IBigNumber second)
    {
      throw new NotImplementedException();
    }

    public IBigNumber IClone()
    {
      return new RationalBigNumber<T>(this);
    }

    public IBigNumber IConstruct(string number)
    {
      return new RationalBigNumber<T>(number);
    }

    public void IAbs()
    {
      IsNegative = false;
    }

    public bool IEqual(IBigNumber second)
    {
      return this == (second as RationalBigNumber<T>);
    }

    public bool ISmaller(IBigNumber second)
    {
      throw new NotImplementedException();
      //return this < (second as StdRationalNumber<T>);
    }
    #endregion

    #region IComparable<RationalBigNumber<T>> Members

    public int CompareTo(RationalBigNumber<T> other)
    {
      if (other.IEqual(this)) return 0;
      if (other < this) return 1;
      return -1;
    }

    #endregion
  }

}
